	TITLE	'FINDBAD Ver 1.0 - Universal Version for CP/M-86'
;
;		    FINDBAD.A86 Ver. 1.0
;
;    Modified for CP/M-86 from FINDBAD52.ASM of 02/16/82
;
;FINDBAD will find all bad blocks on a disk and build a file
;named [UNUSED].BAD to allocate them, thus "locking out" the
;bad blocks so CP/M-86 will not use them.
;
;Originally written by Gene Cotton,  published in "Interface
;Age", September 1980 issue, page 80.
;
;               SYSTST and BADUSR options:
;
;  Many double-density disk systems have single-density system 
;tracks.  If this is true with your system, you can change the 
;program to skip the system tracks,  without re-assembling it.  
;To do this, set the byte at 103H to a 0 if you don't want the 
;system  tracks tested,  otherwise leave it 0.   This is  also 
;necessary if you have a "blocked" disk system;  that is, when 
;the same physical disk is seperated into logical disks by use 
;of the SYSTRK word in the disk parameter block.
;
;   If  you  are a CP/M-86 1.x user,  you may assign the  user 
;number  where  [UNUSED.BAD] will be created by  changing  the  
;byte  at 104H  to  the  desired user number.   If you want it  
;in  the default user,  then leave it 0FFH.
;
;
;-------------------------------------------------------------
;NOTE: If you want to  update this program, make sure you have 
;the latest version first.   After adding your changes, please 
;modem  a  copy of the new file to CP/M-NET  in  Simi  Valley, 
;California - phone (805) 527-9321 (50,  110,  300, 450 or 600 
;baud). Use the filename FNDBDA86.NEW. (K. Smith)
;
;Modifications/updates:
;
;02/03/82  Changed to CP/M-86 compatibility with the help  of 
;          DR's XLT86 utility, made changes for goofs made by 
;           XLT86,  removed ANYTHING to do with CP/M-80  Ver. 
;          1.4. (K. Smith, CP/M-Net 'SYSOP')
; 
;         Note: You must assemble with ASM86.CMD as follows:
;
;               ASM86 FINDBAD<cr>
;
;               Then create a '.CMD' as,
;
;               GENCMD FINDBAD 8080<cr>
;
;04/10/81 Changed extent DB from -1 to 0FFH so program can be
;	  assembled by ASM.  Added BADUSR info to instructions
;	  for altering with DDT.  (KBP)
;
;04/09/81 Changed sign-on message, added control-c abort test,
;	  added '*' to console once each track	(RGF)
;
;04/07/81 Re-wrote to add the following features:
;		1) "universal" operation
;		2) DDT-changeable "SYSTRK" boolean (see above)
;		3) Report to console when bad blocks are detected
;		4) Changed the method of printing the number of
;		   bad blocks found (at end of run)...the old
;		   method used too much code, and was too cum-
;		   bersome.
;		5) Made several cosmetic changes
;
;			Ron Fowler
;			Westland, Mich
;
;03/23/81 Set equates to standard drive and not double-sided. (KBP)
;
;03/01/81 Corrected error for a Horizon with double sided drive.
;	  This uses 32k extents, which code did not take into account.
;	  (Bob Clyne)
;
;02/05/81 Merged 2/2/81 and 1/24/81 changes, which were done
;	  independently by Clyne and Mack.  (KBP)
;
;02/02/81 Added equates for North Star Horizon - 5.25" drives,
;	  double density, single and double sided. (Bob Clyne)
;
;01/24/81 Added equates for Jade DD disk controller
;	  (Pete H. Mack)
;
;01/19/81 Added equates for Icom Microfloppy 5.25" drives.
;	  (Eddie Currie)
;
;01/05/81 Added equates for Heath H-17 5.25" drives.
;	  (Ben Goldfarb)
;
;12/08/80 Added equates for National Multiplex D3S/D4S
;	  double-density board in various formats.
;	  (David Fiedler)
;
;09/22/80 Added equates for Morrow Disk Jockey 2D/SS, 256,
;	  512 and 1024-byte sector options.  Fix 'S2' update
;	  flag for larger max number of extents. Cleaned up
;	  file. (Ben Bronson and KBP)
;
;09/14/80 Corrected DGROUP equate for MMDBL. Added new routine
;	  to correct for IMDOS group allocation.  Corrected
;	  error in instructions for using TEST routine.
;	  (CHS) (AJ) (KBP) - (a group effort)
;
;09/08/80 Fixed several errors in Al Jewer's mods.  Changed
;	  return to CP/M to warm boot so bitmap in memory will
;	  be properly updated. Added conditional assembly for
;	  testing program. (KBP)
;
;09/02/80 Added IMDOS double-density equates & modified for 
;	  more then 256 blocks per disk. (Al Jewer)
;
;09/01/80 Changed equates so that parameters are automatically
;	  set for each disk system conditional assembly (KBP)
;
;08/31/80 Add conditional assembly for Digital Microsystems FDC3
;	  controller board in double-density format and fix to
;	  do 256 blocks in one register. (Thomas V. Churbuck)
;
;08/31/80 Correct MAXB equate - MAXB must include the directory
;	  blocks as well as the data blocks.  Fix to make sure
;	  any [UNUSED].BAD file is erased before data area is
;	  checked. (KBP)
;
;08/30/80 Added conditional assembly for Micromation
;	  double-density format. (Charles H. Strom)
;
;08/27/80 Fix missing conditional assembly in FINDB routine.
;	  Put version number in sign-on message. (KBP)
;
;08/26/80 Modified by Keith Petersen, W8SDZ, to:
;	  (1) Add conditional assembly for 1k/2k groups
;	  (2) Add conditional assembly for standard drives
;	      and Micropolis MOD II
;	  (3) Make compatible with CP/M-2.x
;	  (4) Remove unneeded code to check for drive name
;	      (CP/M does it for you and returns it in the FCB)
;	  (5) Changed to open additional extents as needed for
;	      overflow, instead of additional files
;	  (6) Add conditional assembly for system tracks check
;	      (some double-density disks have single-density
;	      system tracks which cannot be read by this program)
;	  (7) Increased stack area (some systems use more than
;	      others).
;
;08/06/80 Added comments and crunched some code.
;	  KELLY SMITH.	805-527-9321 (Modem, 300 Baud)
;			805-527-0518 (Verbal)
;
;
;			Using the Program
;
; Before  using this program to "reclaim" a diskette,  it  is
;recommended that the diskette be reformatted. If this is not
;possible,  at least assure yourself that any existing	files
;on the diskette  do not contain unreadable  sectors.  If you
;have changed disks since the last warm-boot, you  must warm-
;boot again before running this program.
;
; To  use the program,	insert	both the disk containing  the
;program  FINDBAD.CMD and the diskette to be checked into the
;disk drives. It is possible that the diskette containing the
;program is the one to be checked. Assume that the program is
;on drive "A" and the suspected bad disk is on drive "B".  In
;response to the CP/M prompt "A>",  type in FINDBAD B:.  This
;will  load the file FINDBAD.CMD from drive "A" and test  the
;diskette  on  drive "B" for  unreadable  sectors.  The  only
;allowable  parameter  after  the  program name  is  a	drive
;specification	(of the form " N:") for up to four (A  to  D)
;disk drives.  If no drive is specified, the currently logged
;in drive is assumed to contain the diskette to check.
;
; The  program first checks the CP/M System tracks (0 and 1),
;and  any  errors here prohibit the disk from being  used  on
;drive	"A",  since all "warm  boots" occur using the  system
;tracks from the "A" drive.
;
; The  program next checks the first two data blocks  (groups
;to some of us) containing the directory of the diskette.  If
;errors  occur	here,  the  program  terminates  and  control
;returns  to  CP/M  (no other data blocks are  checked	since
;errors in the directory render the disk useless).
;
; Finally,  all  the remaining data blocks are	checked.  Any
;sectors  which  are  unreadable cause the data  block	which
;contains them to be stored temporarily as a "bad block".  At
;the end of this phase,  the message "XX bad blocks found" is
;displayed (where XX is replaced by the number of bad blocks,
;or "No" if no read errors occur).  If bad blocks occur,  the
;filname [UNUSED].BAD is created, the list of "bad blocks" is
;placed  in  the allocation map of the	directory  entry  for
;[UNUSED].BAD,	and the file is closed.  Note,	that when the
;number of "bad blocks" exceeds 16,  the  program  will  open
;additional  extents  as  required  to	hold the overflow.  I
;suggest that if the diskette has more than  32 "bad blocks",
;perhaps it should be sent to the "big disk drive in the sky"
;for the rest it deserves.
;
; The  nifty part of all this is that if any "bad blocks"  do
;occur, they are allocated to [UNUSED].BAD and no longer will
;be available to CP/M-86 for future allocation... bad sectors
;are logically locked out on the diskette
;
;
;	       Using the TEST conditional assembly
;
;A  conditional  assembly has been added to allow  testing  this 
;program  to  make sure it is reading all sectors on  your  disk 
;that  are accessible to CP/M.	The program reads the disk on  a 
;block by block basis, so it is necessary to first determine the 
;number of blocks present.  To start, we must know the number of 
;sectors/block (8 sectors/block for standard IBM single  density 
;format).  If  this  value  is	not  known,  it  can  easily  be 
;determined  by saving one page in a test file and interrogating 
;using the STAT command:
;
;For standard single-density STAT will report this file as being
;1k.  The file size reported (in bytes) is the size of a  block. 
;This  value  divided  by 128 bytes/sector  (the  standard  CP/M 
;sector  size)	will  give sectors/block.  For	our  IBM  single 
;density example, we have:
;
;  (1024 bytes/block) / (128 bytes/sector) = 8 sectors/block.
;
;We  can now calculate blocks/track (assuming we know the number 
;sectors/track). In our example:
;
;  (26 sectors/track) / (8 sectors/block) = 3.25 blocks/track
;
;Now  armed with the total number of data tracks (75 in our  IBM 
;single density example), we get total blocks accessible: 
;
;  75 (tracks/disk) x (3.25 blocks/track) = 243.75 blocks/disk 
;
;CP/M cannot access a fractional block, so we round down (to 243 
;blocks  in  our  example).  Now  multiplying  total  blocks  by 
;sectors/block	results in total sectors as should  be	reported 
;when TEST is set TRUE and a good disk is read. For our example, 
;this value is 1944 sectors. 
;
;Finally,  note that if SYSTEM is set TRUE,  the sectors present 
;on  the  first  two tracks must be added in  as  well.  In  the 
;previous  example,  this  results in  1944 + 52 = 1996  sectors 
;reported by the TEST conditional.
;
;Run the program on a KNOWN-GOOD disk.	It should report that it
;has read  the	correct number of sectors.  The test conditional
;assembly should then be set FALSE and the program re-assembled.
;The test routines  cannot be left in  because this program does
;not read all the sectors in a block that is found to be bad and
;thus will report an inaccurate number of sectors read.
;
;
;Define TRUE and FALSE
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
;******************************************************************
;
;Conditional assembly switch for testing this program
;(for initial testing phase only - see remarks above)
;
TEST	EQU	FALSE			;TRUE FOR TESTING ONLY
;
;******************************************************************
;
;System equates
;
BASE	EQU	0			;STANDARD CP/M BASE ADDRESS
FCB	EQU	BASE+5CH		;CP/M DEFAULT FCB LOCATION
;
;Define ASCII characters used
;
CR	EQU	0DH			;CARRIAGE RETURN CHARACTER
LF	EQU	0AH			;LINE FEED CHARACTER
TAB	EQU	09H			;TAB CHARACTER
BEL	EQU	07H			;BELL CHARACTER
;
M	EQU	Byte Ptr	0[BX]
;
	cseg
	ORG	BASE+100H
;
	JMPS	START			;JMP AROUND OPTION BYTES
;
;If you want the system tracks tested, then put a 1 here, otherwise 0.
;
SYSTST	DB	1			;0 IF NO SYS TRACKS, OTHERWISE 1
;
;   Change this byte to the user number you want [UNUSED].BAD 
;to reside in.  If you want it in the default user, then leave 
;it 0FFH.
;
BADUSR	DB	0FFH			;USER # WHERE [UNUSED.BAD] GOES
;0FFH = DEFAULT USER
;
START:	CALL	START2			;GO PRINT SIGNON
;
	DB	CR,LF,'FINDBAD - Ver 1.0, Bad Sector Lockout Utility'
	DB	CR,LF
	DB	'------- Universal Version for CP/M-86 -------'
	DB	CR,LF,CR,LF,'Type CTL-C to abort',CR,LF,'$'
;
START2:	POP	DX			;GET MSG ADRS
	MOV	CL,9			;bdos PRINT BUFFER function
	INT	224			;bdos PRINT SIGN-ON MSG
	CALL	SETUP			;SET BIOS ENTRY, AND CHECK DRIVE
	CALL	FINDB			;ESTABLISH ALL BAD BLOCKS
	JZ	NOBAD			;SAY NO BAD BLOCKS, IF SO
	CALL	SETDM			;FIX DM BYTES IN FCB
;
NOBAD:	CALL	CRLF
	MOV	AL,TAB
	CALL	DISPLAY
	MOV	DX,(Offset NOMSG)	;POINT FIRST TO 'NO'
	MOV	BX,Word Ptr BADBKS	;PICK UP # BAD BLOCKS
	MOV	AL,BH			;CHECK FOR ZERO
	OR	AL,BL
	JZ	PMSG1			;JUMP IF NONE
	CALL	DECOUT			;OOPS..HAD SOME BAD ONES, REPORT
	JMPS	PMSG2
;
PMSG1:	MOV	CL,9			;bdos PRINT BUFFER function
	INT	224
;
PMSG2:	MOV	DX,(Offset ENDMSG)	;REST OF EXIT MESSAGE
;
PMSG:	MOV	CL,9
	INT	224
;
	MOV	CL,0			;EXIT TO CP/M WARM BOOT
	MOV	DL,0
	INT	224
;
;Get actual address of BIOS routines
;
SETUP	EQU	$	;Check for drive specification
;
GDRIV:	MOV	AL,Byte Ptr .FCB	;GET DRIVE NAME
	MOV	CL,AL
	OR	AL,AL			;ZERO?
	JNZ	GD2			;IF NOT,THEN GO SPECIFY DRIVE
	MOV	CL,25			;GET LOGGED-IN DRIVE
	INT	224
	INC	AL			;MAKE 1-RELATIVE
	MOV	CL,AL
;
GD2:	CMP	AL,15+1			;CHECK FOR HIGHEST DRIVE NUMBER
	JNAE	GD3	
	JMP	SELERR			;SELECT ERROR
;
GD3:	DEC	CL			;BACK OFF FOR CP/M
	PUSH	CX			;SAVE DISK SELECTION
	MOV	DL,CL			;ALIGN FOR BDOS
	MOV	CL,14			;SELECT DISK function
	INT	224
	POP	CX			;GET BACK DISK NUMBER
;
SETDSK:	MOV	Byte Ptr FUNC,9		;bios select disk function
	MOV	Word Ptr BIOS_DESC,CX	;pass disk number to bios descriptor
	MOV	Word Ptr BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
	MOV	DL,ES: M		;GET SECTOR TABLE PNTR
	LAHF
	INC	BX
	SAHF
	MOV	DH,ES: M
	LAHF
	INC	BX
	SAHF
	XCHG	BX,DX
	MOV	Word Ptr SECTBL,BX	;STORE IT AWAY
	MOV	BX,8			;OFFSET TO DPB POINTER
	LAHF
	ADD	BX,DX
	RCR	SI,1
	SAHF
	RCL	SI,1
	MOV	AL,ES: M			;PICK UP DPB POINTER
	LAHF				;  TO USE
	INC	BX
	SAHF
	MOV	BH,ES: M			;  AS PARAMETER
	MOV	BL,AL			;  TO LOGIT
;
DOLOG:	CALL	LOGIT			;LOG IN DRIVE, GET DISK PARMS
	CALL	GETDIR			;CALCULATE DIRECTORY INFORMATION
;
;
HOMDSK:	MOV	Byte Ptr FUNC,8		;bios HOME DISK function
	MOV	Word Ptr BIOS_DESC,0	;pass 'nothing' number to bios descriptor
	MOV	Word Ptr BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
;Now set the required user number
;
	MOV	AL,Byte Ptr BADUSR	;GET THE USER NUMBER
	CMP	AL,0FFH			;IF IT IS 0FFH, THEN RETURN
	JNZ	L_6
	RET
L_6:
	MOV	CL,32			;GET/SET USER CODE
	INT	224
	RET
;
;Look for bad blocks
;
FINDB:	MOV	AL,Byte Ptr SYSTST
	OR	AL,AL
	JZ	DODIR			;JUMP IF NO SYS TRACKS TO BE TESTED
	CALL	CHKSYS			;CHECK FOR BAD BLOCKS ON TRACK 0 AND 1
;
DODIR:	CALL	CHKDIR			;CHECK FOR BAD BLOCKS IN DIRECTORY
	CALL	TELL1
;
	DB	CR,LF,'Testing data area...',CR,LF,'$'
;
TELL1:	POP	DX
	MOV	CL,9			;bdos PRINT STRING function
	INT	224
	CALL	ERAB			;ERASE ANY [UNUSED].BAD FILE
	MOV	BX,Word Ptr DIRBKS	;START AT FIRST DATA BLOCK
	MOV	CX,BX			;PUT INTO [CX]
;
FINDBA:	CALL	READB			;READ THE BLOCK
	JZ	L_7	
	CALL	SETBD			;IF BAD, ADD BLOCK TO LIST
L_7:
	LAHF				;BUMP TO NEXT BLOCK
	INC	CX
	SAHF
	MOV	BX,Word Ptr DSM
	MOV	DX,CX			;SET UP FOR (MAXGRP - CURGRP)
	SBB	BX,DX			;DO SUBTRACT: (MAXGRP - CURGRP)
	JNB	FINDBA			;UNTIL CURGRP>MAXGRP
	CALL	CRLF
	MOV	BX,Word Ptr DMCNT	;GET NUMBER OF BAD SECTORS
	MOV	AL,BH
	OR	AL,BL			;SET ZERO FLAG, IF NO BAD BLOCKS
	RET				;RETURN FROM "FINDB"
;
;Check system tracks, notify user if bad, but continue
;
CHKSYS:	CALL	CHSY1			;bdos PRINT MESSAGE
;
	DB	CR,LF,'Testing system tracks...',CR,LF,'$'
;
CHSY1:	POP	DX
	MOV	CL,9			;bdos PRINT STRING function
	INT	224
	MOV	BX,0			;SET TRACK 0, SECTOR 1
	MOV	Word Ptr TRACK,BX
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr SECTOR,BX
;
CHKSY1:	CALL	READS			;READ A SECTOR
	JNZ	SYSERR			;NOTIFY, IF BAD BLOCKS HERE
	MOV	BX,Word Ptr SYSTRK	;SET UP (TRACK-SYSTRK)
	XCHG	BX,DX
	MOV	BX,Word Ptr TRACK
	SBB	BX,DX			;DO THE SUBTRACT
	JB	CHKSY1			;LOOP WHILE TRACK < SYSTRK
	RET				;RETURN FROM "CHKSYS"
;
SYSERR:	MOV	DX,(Offset ERMSG5)	;SAY NO GO, AND BAIL OUT
	MOV	CL,9			;bdos PRINT BUFFER function
	INT	224
	RET				;RETURN FROM "SYSERR"
;
;Check for bad blocks in directory area
;
CHKDIR:	CALL	CHKD1
;
	DB	CR,LF,'Testing directory area...',CR,LF,'$'
;
CHKD1:	POP	DX
	MOV	CL,9			;bdos PRINT STRING function
	INT	224
	MOV	CX,0			;START AT BLOCK 0
;
CHKDI1:	CALL	READB			;READ A BLOCK
	JZ	L_8	
	JMP	ERROR6			;IF BAD, INDICATE ERROR IN DIRECTORY AREA
L_8:
	LAHF				;BUMP FOR NEXT BLOCK
	INC	CX
	SAHF
	MOV	BX,Word Ptr DIRBKS	;SET UP (CURGRP - DIRBKS)
	LAHF				;MAKE 0-RELATIVE
	DEC	BX
	SAHF
	MOV	DX,CX
	SBB	BX,DX			;DO THE SUBTRACT
	JNB	CHKDI1			;LOOP UNTIL CURGRP > DIRGRP
	RET				;RETURN FROM "CHKDIR"
;
;Read all sectors in block, and return zero flag set if none bad
;
READB:	CALL	CNVRTB			;CONVERT TO TRACK/SECTOR IN H&L REGS.
	MOV	AL,Byte Ptr BLM
	INC	AL			;NUMBER OF SECTORS/BLOCK
	MOV	DH,AL			;  IN D REG
;
READBA:	PUSH	DX
	CALL	READS			;READ SKEWED SECTOR
	POP	DX
	JZ	L_9	
	RET				;ERROR IF NOT ZERO...
L_9:
	DEC	DH			;DEBUMP SECTOR/BLOCK
	JNZ	READBA			;DO NEXT, IF NOT FINISHED
	RET				;RETURN FROM "READBA"
;
;Convert block number to track and skewed sector number
;
CNVRTB:	PUSH	CX			;SAVE CURRENT GROUP
	MOV	BX,CX			;NEED IT IN [BX], FOR EASY SHIFTING
	MOV	AL,Byte Ptr BSH		;DPB VALUE THAT TELLS HOW TO
;
SHIFT:	SHL	BX,1			;  SHIFT GROUP NUMBER TO GET
	DEC	AL			;  DISK-DATA-AREA RELATIVE
	JNZ	SHIFT			;  SECTOR NUMBER
	XCHG	BX,DX			;REL SECTOR # INTO DE
	MOV	BX,Word Ptr SPT		;SECTORS PER TRACK FROM DPB
	NOT	BX			;1ST 1'S COMPLEMENT...
	INC	BX			;...THEN 2'S COMPLEMENT
	XCHG	BX,DX
	MOV	CX,0			;INITIALIZE QUOTIENT
;
;Divide by number of sectors
;	quotient = track
;	     mod = sector
;
DIVLP:	LAHF				;DIRTY DIVISION
	INC	CX
	SAHF
	LAHF
	ADD	BX,DX
	RCR	SI,1
	SAHF
	RCL	SI,1
	JB	DIVLP
	LAHF				;FIXUP LAST
	DEC	CX
	SAHF
	XCHG	BX,DX
	MOV	BX,Word Ptr SPT
	LAHF
	ADD	BX,DX
	SAHF
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr SECTOR,BX	;NOW HAVE LOGICAL SECTOR
	MOV	BX,Word Ptr SYSTRK	;BUT BEFORE WE HAVE TRACK #,
	LAHF				;  WE HAVE TO ADD SYS TRACK OFFSET
	ADD	BX,CX
	RCR	SI,1
	SAHF
	RCL	SI,1
	MOV	Word Ptr TRACK,BX
	POP	CX			;THIS WAS OUR GROUP NUMBER
	RET
;
;READS reads a logical sector (if it can)
;and returns zero flag set if no error.
;
READS:	PUSH	CX			;SAVE THE GROUP NUMBER
;
	CALL	LTOP			;CONVERT LOGICAL TO PHYSICAL
	MOV	BX,Word Ptr PHYSEC	;GET PHYSICAL SECTOR
	MOV	CX,BX			;INTO [CX]
;
SETSEC:	MOV	BYTE PTR FUNC,11	;bios SET SECTOR function
	MOV	WORD PTR BIOS_DESC,CX	;pass sector number to bios descriptor
	MOV	WORD PTR BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
	MOV	BX,Word Ptr TRACK	;NOW SET THE TRACK
	MOV	CX,BX			;CP/M WANTS IT IN [CX]
;
SETTRK:	MOV	BYTE PTR FUNC,10	;bios SELECT TRACK function
	MOV	WORD PTR BIOS_DESC,CX	;pass track number to bios descriptor
	MOV	WORD PTR BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
;Now do the sector read
;
DREAD:	MOV	BYTE PTR FUNC,13	;bios READ SECTOR function
	MOV	WORD PTR BIOS_DESC,0	;pass 'nothing' number to bios descriptor
	MOV	WORD PTR BIOS_DESC+2,0	;fill remaining descriptor (DX) zero
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
	OR	AL,AL			;SET FLAGS
	LAHF				;SAVE ERROR FLAG
	XCHG	AL,AH
	PUSH	AX
	MOV	BX,Word Ptr SECTOR	;GET LOGICAL SECTOR #
	LAHF				;WE WANT TO INCREMENT TO NEXT
	INC	BX
	SAHF
	XCHG	BX,DX			;BUT FIRST...CHECK OVERFLOW
	MOV	BX,Word Ptr SPT		;  BY DOING (SECPERTRK-SECTOR)
	SBB	BX,DX			;DO THE SUBTRACTION
	XCHG	BX,DX
	JNB	NOOVF			;JUMP IF NOT SECTOR>SECPERTRK
;
;Sector overflow...bump track number, reset sector
;
	MOV	BX,Word Ptr TRACK
	LAHF
	INC	BX
	SAHF
	MOV	Word Ptr TRACK,BX
	MOV	AL,'*'			;TELL CONSOLE ANOTHER TRACK DONE
	CALL	DISPLAY
	CALL	STOP			;SEE IF CONSOLE WANTS TO QUIT
	MOV	BX,1			;NEW SECTOR NUMBER ON NEXT TRACK
;
NOOVF:	MOV	Word Ptr SECTOR,BX	;PUT SECTOR AWAY
	POP	AX			;GET BACK ERROR FLAGS
	XCHG	AL,AH
	SAHF
	POP	CX			;RESTORE GROUP NUMBER
	RET
;
;Convert logical sector # to physical
;
LTOP:	MOV	BX,Word Ptr SECTBL	;SET UP PARAMETERS
	XCHG	BX,DX			;  FOR CALL TO SECTRAN
	MOV	BX,Word Ptr SECTOR
	MOV	CX,BX
	DEC	CX			;ALWAYS CALL SECTRAN W/ZERO-REL SEC #
;
SECT1:	CALL	SECTRN			;DO THE SECTOR TRANSLATION
	MOV	AL,Byte Ptr SPT+1	;CHECK IF BIG TRACKS
	OR	AL,AL			;SET FLAGS (TRACKS > 256 SECTORS)
	JNZ	LTOP1			;NO SO SKIP
	MOV	BH,AL			;ZERO OUT UPPER 8 BITS
;
LTOP1:	MOV	Word Ptr PHYSEC,BX	;PUT AWAY PHYSICAL SECTOR
	RET
;
;Sector translation vector
;
SECTRN:	MOV	BYTE PTR FUNC,16	;bios SECTOR TRANSLATE function
	MOV	WORD PTR BIOS_DESC,CX	;pass sector to bios descriptor
	MOV	WORD PTR BIOS_DESC+2,DX	;translate table offset
	MOV	DX,(Offset FUNC)	;point to function parameter block
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
	ret				;return from 'sectrn'
;
;Put bad block in bad block list
;
SETBD:	PUSH	CX
	CALL	SETBD1
	DB	CR,LF,BEL,'Bad block: $'
;
SETBD1:	POP	DX			;RETRIEVE ARG
	MOV	CL,9			;bdos PRINT STRING
	INT	224
	POP	CX			;GET BACK BLOCK NUMBER
	MOV	AL,CH
	CALL	HEXO			;bdos PRINT IN HEX
	MOV	AL,CL
	CALL	HEXO
	CALL	CRLF
	MOV	BX,Word Ptr DMCNT	;GET NUMBER OF SECTORS
	MOV	AL,Byte Ptr BLM		;GET BLOCK SHIFT VALUE
	INC	AL			;MAKES SECTOR/GROUP VALUE
	MOV	DL,AL			;WE WANT 16 BITS
	MOV	DH,0
	ADD	BX,DX			;BUMP BY NUMBER IN THIS BLOCK
	MOV	Word Ptr DMCNT,BX	;UPDATE NUMBER OF SECTORS
	MOV	BX,Word Ptr BADBKS	;INCREMENT NUMBER OF BAD BLOCKS
	INC	BX
	MOV	Word Ptr BADBKS,BX
	MOV	BX,Word Ptr DMPTR	;GET POINTER INTO DM
	MOV	M,CL			;...AND PUT BAD BLOCK NUMBER
	INC	BX			;BUMP TO NEXT AVAILABLE EXTENT
	MOV	AL,Byte Ptr DSM+1	;CHECK IF 8 OR 16 BIT BLOCK SIZE
	OR	AL,AL
	JZ	SMGRP			;JUMP IF 8 BIT BLOCKS
	MOV	M,CH			;ELSE STORE HI BYTE OF BLOCK #
	LAHF				;AND BUMP POINTER
	INC	BX
	SAHF
;
SMGRP:	MOV	Word Ptr DMPTR,BX	;SAVE DM POINTER, FOR NEXT TIME THROUGH HERE
	RET				;RETURN FROM "SETBD"
;
;Eliminate any previous [UNUSED].BAD entries
;
ERAB:	MOV	DX,(Offset BFCB)	;POINT TO BAD FCB
	MOV	CL,19			;bdos DELETE FILE function
	INT	224
	RET
;
;Create [UNUSED].BAD file entry
;
OPENB:	MOV	DX,(Offset BFCB)	;POINT TO BAD FCB
	MOV	CL,22			;bdos MAKE FILE function
	INT	224
	CMP	AL,0FFH			;CHECK FOR OPEN ERROR
	JZ	L_10	
	RET				;RETURN FROM "OPENB", IF NO ERROR
L_10:
	JMP	ERROR7			;BAIL OUT...CAN'T CREATE [UNUSED].BAD
;
CLOSEB:	XOR	AL,AL
	MOV	AL,Byte Ptr BFCB+14	;GET CP/M 'S2' BYTE
	AND	AL,1FH			;ZERO UPDATE FLAGS
	MOV	Byte Ptr BFCB+14,AL	;RESTORE IT TO OUR FCB
	MOV	DX,(Offset BFCB)	;FCB FOR [UNUSED].BAD
	MOV	CL,16			;bdos CLOSE FILE function
	INT	224
	RET				;RETURN FROM "CLOSEB"
;
;Move bad area DM to BFCB
;
SETDM:	MOV	BX,(Offset DM)		;GET DM
	MOV	Word Ptr DMPTR,BX	;SAVE AS NEW POINTER
	MOV	AL,Byte Ptr EXM		;GET THE EXTENT SHIFT FACTOR
	MOV	CL,0			;INIT BIT COUNT
	CALL	COLECT			;GET SHIFT VALUE
	MOV	BX,128			;STARTING EXTENT SIZE
	MOV	AL,CL			;FIRST SEE IF ANY SHIFTS TO DO
	OR	AL,AL
	JZ	NOSHFT			;JUMP IF NONE
;
ESHFT:	SHL	BX,1			;SHIFT
	DEC	AL			;BUMP
	JNZ	ESHFT			;LOOP
;
NOSHFT:	PUSH	BX			;SAVE THIS, IT IS RECORDS PER EXTENT
	MOV	AL,Byte Ptr BSH		;GET BLOCK SHIFT
	MOV	CH,AL
;
BSHFT:	CLC				;CLEAR CARRY FLAG
	RCR	BX,1			;SHIFT RIGHT
	DEC	CH
	JNZ	BSHFT			;TO GET BLOCKS PER EXTENT
	MOV	AL,BL			;IT'S IN [BL] (CAN'T BE >16)
	MOV	Byte Ptr BLKEXT,AL	;SET BLOCK EXTENT, WILL NEED THIS LATER
	POP	BX			;GET BACK REC/EXT
;
SET1:	XCHG	BX,DX			;NOW HAVE REC/EXTENT IN [DX]
	MOV	BX,Word Ptr DMCNT	;COUNT OF BAD SECTORS
;
SETDMO:	PUSH	BX			;SET FLAGS ON (DMCNT-BADCNT)
	SBB	BX,DX			;HAVE TO SUBTRACT FIRST
	MOV	CX,BX			;SAVE RESULT IN [CX]
	POP	BX			;THIS POP MAKES IT COMPARE ONLY
	JB	SETDME			;JUMP IF LESS THAN 1 EXTENT WORTH
	MOV	AL,CH
	OR	AL,CL			;TEST IF SUBTRACT WAS 0
	JZ	EVENEX			;EXTENT IS EXACTLY FILLED (SPL CASE)
	MOV	BX,CX			;RESTORE RESULT TO [BX]
	PUSH	BX			;SAVE TOTAL
	PUSH	DX			;AND SECTORS/EXTENT
	XCHG	BX,DX
	CALL	SETDME			;PUT AWAY ONE EXTENT
	XCHG	BX,DX
	MOV	Word Ptr DMPTR,BX	;PUT BACK NEW DM POINTER
	POP	DX			;GET BACK SECTORS/EXTENT
	POP	BX			;AND COUNT OF BAD SECTORS
	JMPS	SETDMO			;AND LOOP
;
;Handle the special case of a file that ends on an extent
;boundary.  CP/M requires that such a file have a succeeding
;empty extent in order for the BDOS to properly access the file.
;
EVENEX:	XCHG	BX,DX			;FIRST SET EXTENT W/BAD BLOCKS
	CALL	SETDME
	XCHG	BX,DX
	MOV	Word Ptr DMPTR,BX
	MOV	BX,0			;NOW SET ONE WITH NO DATA BLOCKS
;
;Fill in an extent's worth of bad sectors/block numbers.
;Also fill in the extent number in the FCB.
;
SETDME:	PUSH	BX			;SAVE RECORD COUNT
	MOV	AL,Byte Ptr EXTNUM	;UPDATE EXTENT BYTE
	INC	AL
	MOV	Byte Ptr EXTNUM,AL	;SAVE FOR LATER
	MOV	Byte Ptr BFCB+12,AL	; AND PUT IN FCB
	CALL	OPENB			;OPEN THIS EXTENT
	POP	BX			;RETRIEVE REC COUNT
;
;Divide record count by 128 to get the number
;of logical extents to put in the EX field
;
	MOV	CH,0			;INIT QUOTIENT
	MOV	DX,-128			;-DIVISOR
	MOV	AL,BH			;TEST FOR SPL CASE
	OR	AL,BL			;  OF NO RECORDS
	JZ	SKIP
;
DIVLOP:	ADD	BX,DX			;SUBTRACT
	INC	CH			;BUMP QUOTIENT
	JB	DIVLOP
	MOV	DX,128			;FIX UP OVERSHOOT
	ADD	BX,DX
	DEC	CH
	MOV	AL,BH			;TEST FOR WRAPAROUND
	OR	AL,BL
	JNZ	SKIP
	MOV	BL,80H			;RECORD LENGTH
	DEC	CH
;
SKIP:	MOV	AL,Byte Ptr EXTNUM	;NOW FIX UP EXTENT NUM
	ADD	AL,CH
	MOV	Byte Ptr EXTNUM,AL
	MOV	Byte Ptr BFCB+12,AL
	MOV	AL,BL			;MOD IS RECORD COUNT
	MOV	Byte Ptr BFCB+15,AL	;THAT GOES IN RC BYTE
;
MOVDM:	MOV	AL,Byte Ptr BLKEXT	;GET BLOCKS PER EXTENT
	MOV	CH,AL			;INTO B
;
SETD1:	MOV	BX,Word Ptr DMPTR	;POINT TO BAD ALLOCATION MAP
	XCHG	BX,DX
	MOV	BX,(Offset BFCB)+16	;DISK ALLOC MAP IN FCB
;
SETDML:	MOV	SI,DX
	MOV	AL,[SI]
	MOV	M,AL
	INC	BX
	INC	DX
;
;Now see if 16 bit groups...if so,
;we have to move another byte
;
	MOV	AL,Byte Ptr DSM+1	;THIS TELLS US
	OR	AL,AL
	JZ	BUMP1			;IF ZERO, THEN NOT
	MOV	SI,DX			;IS 16 BITS, SO DO ANOTHER
	MOV	AL,[SI]
	MOV	M,AL
	LAHF
	INC	BX
	SAHF
	LAHF
	INC	DX
	SAHF
;
BUMP1:	DEC	CH			;COUNT DOWN
	JNZ	SETDML
	PUSH	DX
	CALL	CLOSEB			;CLOSE THIS EXTENT
	POP	DX
	RET
;
;Error messages
;
SELERR:	MOV	DX,(Offset SELEMS)	;SAY NO GO, AND BAIL OUT
	JMP	PMSG
;
SELEMS	DB	CR,LF,'Drive specifier out of range$'
;
ERMSG5	DB	CR,LF,BEL,'+++ Warning...System tracks'
	DB	' bad +++',CR,LF,CR,LF,'$'
;
ERROR6:	MOV	DX,(Offset ERMSG6)	;OOPS...CLOBBERED DIRECTORY
	JMP	PMSG
;
ERMSG6	DB	CR,LF,BEL,'Bad directory area, try reformatting$'
;
ERROR7:	MOV	DX,(Offset ERMSG7)	;SAY NO GO, AND BAIL OUT
	JMP	PMSG
;
ERMSG7	DB	CR,LF,'Can''t create [UNUSED].BAD$'
;
;
;==== SUBROUTINES ====
;
;Decimal output routine
;
DECOUT:	PUSH	CX
	PUSH	DX
	PUSH	BX
	MOV	CX,-10
	MOV	DX,-1
;
DECOU2:	ADD	BX,CX
	INC	DX
	JB	DECOU2
	MOV	CX,10
	ADD	BX,CX
	XCHG	BX,DX
	MOV	AL,BH
	OR	AL,BL
	JZ	L_17	
	CALL	DECOUT
L_17:
	MOV	AL,DL
	ADD	AL,'0'
	CALL	DISPLAY
	POP	BX
	POP	DX
	POP	CX
	RET
;
;Carriage-return/line-feed to console
;
CRLF:	MOV	AL,CR
	CALL	DISPLAY
	MOV	AL,LF			;FALL INTO 'DISPLAY'
;
DISPLAY:PUSH	CX
	PUSH	DX
	PUSH	BX
	MOV	DL,AL			;CHARACTER TO [DX] FOR CP/M
	MOV	CL,2			;bdos PRINT CONSOLE function
	INT	224			;bdos PRINT CHARACTER
	POP	BX
	POP	DX
	POP	CX
	RET
;
;Subroutine to test console for control-c abort
;
STOP:	MOV	BYTE PTR FUNC,2		;BIOS CONSOLE STATUS FUNCTION
	MOV	WORD PTR BIOS_DESC,0	;PASS 'NOTHING' NUMBER TO BIOS DESCRIPTOR
	MOV	WORD PTR BIOS_DESC+2,0	;FILL REMAINING DESCRIPTOR (DX) ZERO
	MOV	DX,(OFFSET FUNC)	;POINT TO FUNCTION PARAMETER BLOCK
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
	OR	AL,AL			;TEST FLAGS ON ZERO
	JNZ	L_18	
	RET				;RETURN IF NO CHAR
L_18:	MOV	BYTE PTR FUNC,3		;BIOS READ CONSOLE CHARACTER FUNCTION
	MOV	WORD PTR BIOS_DESC,0	;PASS 'NOTHING' NUMBER TO BIOS DESCRIPTOR
	MOV	WORD PTR BIOS_DESC+2,0	;FILL REMAINING DESCRIPTOR (DX) ZERO
	MOV	DX,(OFFSET FUNC)	;POINT TO FUNCTION PARAMETER BLOCK
	MOV	CL,50			;direct bios call
	INT	224			;do it, to it...
;
	CMP	AL,'C'-40H		;IS IT CONTROL-C?
	JZ	L_19	
	RET				;RETURN IF NOT
L_19:
	MOV	DX,(Offset ABORTM)	;EXIT WITH MESSAGE
	JMP	PMSG
;
ABORTM	DB	CR,LF
	DB	'Test aborted by control-C'
	DB	CR,LF,'$'
;
;Move from [BX] to [DX], Count in [CX]
;
MOVE:	MOV	AL,ES: M
	MOV	SI,DX
	MOV	[SI],AL
	LAHF
	INC	BX
	SAHF
	LAHF
	INC	DX
	SAHF
	DEC	CH
	JNZ	MOVE
	RET
;
;Print byte in accumulator in hex
;
HEXO:	LAHF				;SAVE FOR SECOND HALF
	XCHG	AL,AH
	PUSH	AX
	XCHG	AL,AH
	ROR	AL,1			;MOVE INTO POSITION
	ROR	AL,1
	ROR	AL,1
	ROR	AL,1
	CALL	NYBBLE			;bdos PRINT MS NYBBLE
	POP	AX
	XCHG	AL,AH
;
NYBBLE:	AND	AL,0FH			;LO NYBBLE ONLY
	ADD	AL,90H
	DAA
	ADC	AL,40H
	DAA
	JMP	DISPLAY			;bdos PRINT IN HEX
;
;Subroutine to determine the number
;of groups reserved for the directory
;
GETDIR:	MOV	CL,0			;INIT BIT COUNT
	MOV	AL,Byte Ptr AL0		;READ DIR GRP BITS
	CALL	COLECT			;COLLECT COUNT OF DIR GRPS..
	MOV	AL,Byte Ptr AL1		;..IN REGISTER [CL]
	CALL	COLECT
	MOV	BL,CL
	MOV	BH,0			;[CX] NOW HAS A DEFAULT START GRP #
	MOV	Word Ptr DIRBKS,BX	;SAVE FOR LATER
	RET
;
;Collect the number of '1' bits in A as a count in [CL]
;
COLECT:	MOV	CH,8
;
COLOP:	RCL	AL,1
	JNB	COSKIP
	INC	CL
;
COSKIP:	DEC	CH
	JNZ	COLOP
	RET
;
;Routine to fill in disk parameters
;
LOGIT:	MOV	DX,(Offset DPB)		;   THEN MOVE TO LOCAL
	MOV	CH,(Offset DPBLEN)	;  WORKSPACE
	CALL	MOVE
	RET
;
L_22	EQU	$
	dseg	$
	ORG	Offset L_22
;
;--------------------------------------------------
;The disk parameter block is moved here from CP/M-86
;
DPB	EQU	(Offset $)		;DISK PARAMETER BLOCK (COPY)
;
SPT	RS	2			;SECTORS PER TRACK
BSH	RS	1			;BLOCK SHIFT
BLM	RS	1			;BLOCK MASK
EXM	RS	1			;EXTENT MASK
DSM	RS	2			;MAXIMUM BLOCK NUMBER
DRM	RS	2			;MAXIMUM DIRECTORY BLOCK NUMBER
AL0	RS	1			;DIRECTORY ALLOCATION VECTOR
AL1	RS	1			;DIRECTORY ALLOCATION VECTOR
CKS	RS	2			;CHECKED DIRECTORY ENTRIES
SYSTRK	RS	2			;SYSTEM TRACKS
;
;End of disk parameter block
;
DPBLEN	EQU	(Offset $)-(Offset DPB)	;LENGTH OF DISK PARM BLOCK
;
;--------------------------------------------------
BLKEXT	DB	0			;BLOCKS PER EXTENT
DIRBKS	DW	0			;CALCULATED # OF DIR BLOCKS
;
BFCB	DB	0,'[UNUSED]BAD',0,0,0,0
FCBDM	RS	17
;
NOMSG	DB	'No$'
ENDMSG	DB	' bad blocks found',CR,LF,'$'
;
BADBKS	DW	0			;COUNT OF BAD BLOCKS
SECTOR	DW	0			;CURRENT SECTOR NUMBER
TRACK	DW	0			;CURRENT TRACK NUMBER
PHYSEC	DW	0			;CURRENT PHYSICAL SECTOR NUMBER
SECTBL	DW	0			;SECTOR SKEW TABLE POINTER
;
EXTNUM	DB	0FFH			;USED FOR UPDATING EXTENT NUMBER
DMCNT	DW	0			;NUMBER OF BAD SECTORS
DMPTR	DW	(Offset DM)		;POINTER TO NEXT BLOCK ID
;
;storage for 5 byte BIOS function descriptor
;
FUNC		RS	1		;bios function code goes here
BIOS_DESC	RS	2		;[CX] data goes here
		RS	2		;[DX] data goes here
;
DM	EQU	(Offset $)		;BAD BLOCK ALLOCATION MAP
;
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
	DW	0			;RESERVE 2 BYTES
;
	END
